package instruction

import (
	"fmt"
	"log"

	"gitee.com/microwww/jvm-go/jvm/class"
	"gitee.com/microwww/jvm-go/jvm/java"
	"gitee.com/microwww/jvm-go/jvm/rtda"
	"gitee.com/microwww/jvm-go/jvm/tool"
)

func CreateObject(thread *rtda.Thread, clazz *class.Class, argsType string, args ...interface{}) *class.Object {
	obj := class.NewObject(clazz)
	mth := clazz.GetMethod("<init>", argsType)
	fr := rtda.NewMethodFrame(mth)
	thread.PushMethodFrame(fr)
	fr.OperationStack().PushRef(obj)
	fr.LocalVar().SetRef(0, obj)
	for i, v := range args {
		switch t := v.(type) {
		case nil:
			fr.LocalVar().SetRef(uint(i+1), &class.Object{})
		default:
			panic(fmt.Sprintf("Error not supoort type %v", t))
		}
	}
	RunThread(thread)
	return obj
}

type New struct {
	_16_fetch
}

func (f *New) Execute(fr *rtda.MethodFrame) {
	clazz := fr.Method().Class()
	cp := clazz.ConstantPool()
	clz := cp.GetConstant(f.Index).(*class.ClassRef)
	nc := clz.ResolveClass()
	if nc.IsAbstract() || nc.IsInterface() {
		panic("java.lang.InstantiationError")
	}
	ref := class.NewObject(nc)
	fr.OperationStack().PushRef(ref)
}

type PutStatic struct {
	_16_fetch
}

func (f *PutStatic) Execute(fr *rtda.MethodFrame) {
	cp := fr.Method().Class().ConstantPool()
	fi := cp.GetConstant(f.Index).(*class.FieldRef)
	field := fi.ResolveField()
	clazz := field.Class()
	if !field.IsStatic() {
		panic("java.lang.IncompatibleClassChangeError")
	}
	if field.IsFinal() {
		currentMethod := fr.Method()
		if clazz != currentMethod.Class() || currentMethod.Name() != "<clinit>" {
			panic("java.lang.IllegalAccessError")
		}
	}
	log.Default().Printf("Put static field %v.%v", field.Class().Name(), field.Name())
	d := field.Descriptor()
	stack := fr.OperationStack()
	switch d[0] {
	case 'Z', 'B', 'C', 'S', 'I':
		clazz.SetStaticVars(field.Name(), stack.PopInt())
	case 'F':
		clazz.SetStaticVars(field.Name(), stack.PopFloat())
	case 'J':
		clazz.SetStaticVars(field.Name(), stack.PopLong())
	case 'D':
		clazz.SetStaticVars(field.Name(), stack.PopDouble())
	case 'L', '[':
		clazz.SetStaticVars(field.Name(), stack.PopRef())
	default:
		panic("Not support field descriptor: " + d)
	}
}

type GetStatic struct {
	_16_fetch
}

func (f *GetStatic) Execute(fr *rtda.MethodFrame) {
	cp := fr.Method().Class().ConstantPool()
	fi := cp.GetConstant(f.Index).(*class.FieldRef)
	field := fi.ResolveField()
	clazz := field.Class()
	if !field.IsStatic() {
		panic("java.lang.IncompatibleClassChangeError")
	}
	log.Default().Printf("Get static field %v.%v", field.Class().Name(), field.Name())
	d := field.Descriptor()
	stack := fr.OperationStack()
	switch d[0] {
	case 'Z', 'B', 'C', 'S', 'I':
		stack.PushInt(clazz.StaticVars(field.Name()).(int32))
	case 'F':
		stack.PushFloat(clazz.StaticVars(field.Name()).(float32))
	case 'J':
		stack.PushLong(clazz.StaticVars(field.Name()).(int64))
	case 'D':
		stack.PushDouble(clazz.StaticVars(field.Name()).(float64))
	case 'L', '[':
		v := clazz.StaticVars(field.Name())
		if v == nil {
			stack.PushRef(nil)
		} else {
			stack.PushRef(v.(*class.Object))
		}
	default:
		panic("Not support field descriptor: " + d)
	}
}

type PutField struct {
	_16_fetch
}

func (f *PutField) Execute(fr *rtda.MethodFrame) {
	cp := fr.Method().Class().ConstantPool()
	fi := cp.GetConstant(f.Index).(*class.FieldRef)
	field := fi.ResolveField()
	clazz := field.Class()
	if field.IsStatic() {
		panic("java.lang.IncompatibleClassChangeError")
	}
	if field.IsFinal() {
		currentMethod := fr.Method()
		if clazz != currentMethod.Class() || currentMethod.Name() != "<init>" {
			panic("java.lang.IllegalAccessError")
		}
	}
	log.Default().Printf("Put field %v.%v", field.Class().Name(), field.Name())
	d := field.Descriptor()
	stack := fr.OperationStack()
	switch d[0] {
	case 'Z', 'B', 'C', 'S', 'I':
		v := stack.PopInt()
		ref := stack.PopRef()
		if ref == nil {
			panic("java.lang.NullPointerException")
		}
		ref.SetFields(field.Name(), v)
	case 'F':
		// clazz.SetStaticVars(field.Name(), stack.PopFloat())
		v := stack.PopFloat()
		ref := stack.PopRef()
		if ref == nil {
			panic("java.lang.NullPointerException")
		}
		ref.SetFields(field.Name(), v)
	case 'J':
		// clazz.SetStaticVars(field.Name(), stack.PopLong())
		v := stack.PopLong()
		ref := stack.PopRef()
		if ref == nil {
			panic("java.lang.NullPointerException")
		}
		ref.SetFields(field.Name(), v)
	case 'D':
		// clazz.SetStaticVars(field.Name(), stack.PopDouble())
		v := stack.PopDouble()
		ref := stack.PopRef()
		if ref == nil {
			panic("java.lang.NullPointerException")
		}
		ref.SetFields(field.Name(), v)
	case 'L', '[':
		// clazz.SetStaticVars(field.Name(), stack.PopRef())
		v := stack.PopRef()
		ref := stack.PopRef()
		if ref == nil {
			panic("java.lang.NullPointerException")
		}
		ref, ok := ref.LookupFiled(field)
		if !ok {
			panic(fmt.Sprintf("Not find filed %v.%v in %v", field.Class().Name(), field.Name(), ref.Class().Name()))
		}
		ref.SetFields(field.Name(), v)
	default:
		panic("Not support field descriptor: " + d)
	}
}

type GetField struct {
	_16_fetch
}

func (f *GetField) Execute(fr *rtda.MethodFrame) {
	cp := fr.Method().Class().ConstantPool()
	fi := cp.GetConstant(f.Index).(*class.FieldRef)
	field := fi.ResolveField()
	if field.IsStatic() {
		panic("java.lang.IncompatibleClassChangeError")
	}
	log.Default().Printf("Get field %v.%v", field.Class().Name(), field.Name())
	d := field.Descriptor()
	stack := fr.OperationStack()
	ref := stack.PopRef()
	if ref == nil {
		panic("java.lang.NullPointerException")
	}
	ref, ok := ref.LookupFiled(field)
	if !ok {
		panic(fmt.Sprintf("Not find filed %v.%v in %v", field.Class().Name(), field.Name(), ref.Class().Name()))
	}
	switch d[0] {
	case 'Z', 'B', 'C', 'S', 'I':
		stack.PushInt(ref.Field(field).(int32))
	case 'F':
		stack.PushFloat(ref.Field(field).(float32))
	case 'J':
		stack.PushLong(ref.Field(field).(int64))
	case 'D':
		stack.PushDouble(ref.Field(field).(float64))
	case 'L', '[':
		f := ref.Field(field)
		if f == nil {
			stack.PushRef(nil)
		} else {
			stack.PushRef(f.(*class.Object))
		}
	default:
		panic("Not support field descriptor: " + d)
	}
}

type InstanceOf struct {
	_16_fetch
}

func (f *InstanceOf) Execute(fr *rtda.MethodFrame) {
	_, ok := check(f._16_fetch, fr)
	if ok {
		fr.OperationStack().PushInt(1)
	} else {
		fr.OperationStack().PushInt(0)
	}
}

type CheckCast struct {
	_16_fetch
}

func (f *CheckCast) Execute(fr *rtda.MethodFrame) {
	ref, ok := check(f._16_fetch, fr)
	if !ok {
		panic("java.lang.ClassCastException")
	}
	fr.OperationStack().PushRef(ref)
}

func check(f _16_fetch, fr *rtda.MethodFrame) (*class.Object, bool) {
	cp := fr.Method().Class().ConstantPool()
	cr := cp.GetConstant(f.Index).(*class.ClassRef)
	clazz := cr.ResolveClass()
	ref := fr.OperationStack().PopRef()
	if ref != nil {
		if ref.InstanceOf(clazz) {
			return ref, true
		}
	}
	return ref, false
}

// LDC : LOAD Constant
type LDC struct {
	_8_fetch
}

func (f *LDC) Execute(fr *rtda.MethodFrame) {
	ldc(fr, f.Index)
}

// LDC : int, float, String, Class, java.lang.invoke.MethodType, or java.lang.invoke.MethodHandle
func ldc(fr *rtda.MethodFrame, index uint) {
	clz := fr.Method().Class()
	stack := fr.OperationStack()
	ct := clz.ConstantPool().GetConstant(index)
	switch t := ct.(type) {
	case int32:
		stack.PushInt(ct.(int32))
	case float32:
		stack.PushFloat(ct.(float32))
	case string:
		str := java.NewString(clz.ClassLoader(), ct.(string))
		stack.PushRef(str)
	case *class.ClassRef:
		ref := ct.(*class.ClassRef)
		ref.ResolveClass()
		stack.PushRef(ref.Class().Jclass())
	default:
		panic(fmt.Sprintf("TODO: LDC not support : `%#v`", t))
	}
}

type LDC_W struct {
	_16_fetch
}

func (f *LDC_W) Execute(fr *rtda.MethodFrame) {
	ldc(fr, f.Index)
}

type LDC_2_W struct {
	_16_fetch
}

func (f *LDC_2_W) Execute(fr *rtda.MethodFrame) {
	ct := fr.Method().Class().ConstantPool().GetConstant(f.Index)
	switch ct.(type) {
	case int64:
		fr.OperationStack().PushLong(ct.(int64))
	case float64:
		fr.OperationStack().PushDouble(ct.(float64))
	default:
		panic("java.lang.ClassFormatError")
	}
}

// INVOKE_STATIC 调用静态方法
type INVOKE_STATIC struct {
	_16_fetch
}

func (f *INVOKE_STATIC) Execute(fr *rtda.MethodFrame) {
	cp := fr.Method().Class().ConstantPool()
	ref := cp.GetConstant(f.Index).(*class.MethodRef)
	method := ref.ResolveMethod()
	if !method.IsStatic() {
		panic("java.lang.IncompatibleClassChangeError")
	}
	newMethodFrame(fr, method)
	RunThread(fr.Thread())
}

// INVOKE_SPECIAL 无须动态绑定的方法: 构造函数, 私有方法, super 指定的超类方法, 因为这些被调用的方法是编译期可以直接确定的
type INVOKE_SPECIAL struct {
	_16_fetch
}

func (f *INVOKE_SPECIAL) Execute(fr *rtda.MethodFrame) {
	current := fr.Method().Class()
	cp := current.ConstantPool()
	ref := cp.GetConstant(f.Index).(*class.MethodRef)
	method := ref.ResolveMethod()

	obj := fr.OperationStack().GetRef(int32(method.ArgsCount() - 1))
	if obj == nil { // 被调用的方法对应的 `对象`
		panic("java.lang.NullPointerException")
	}

	// 校验方法
	if method.Name() == "<init>" && method.Class() != ref.Class() { // 构造方法只能自己的方法
		panic("java.lang.NoSuchMethodError")
	}
	if method.IsStatic() { // 不允许调用静态方法
		panic("java.lang.IncompatibleClassChangeError")
	}
	if method.IsProtected() {
		// TODO :: 只能被自己, 子类, 或者同一个包调用 否则: IllegalAccessError
	}

	invoker := method
	if method.IsSuper() && method.Name() != "<init>" { // super 关键字调用的方法
		// TODO :: 如果是超类, 只能被子类调用,  "<init>" (构造方法)方法除外 ????
		if current.IsAssignableFrom(method.Class()) { // 必须是派生关系
			panic("java.lang.IllegalAccessError")
		}
		invoker = class.LookupMethodInClass(current.SuperClass(), method.Name(), method.Descriptor())
	}
	if invoker == nil || invoker.IsAbstract() {
		panic("java.lang.AbstractMethodError")
	}

	// 创建一个新的帧, 并将参数推到新的栈中
	nf := newMethodFrame(fr, method)
	log.Default().Printf("Invoke SPECIAL method %v.%v in `%v`", method.Class().Name(), method.Name(), nf.LocalVar().GetRef(0).Class().Name())
	RunThread(nf.Thread()) // to runing ......
}

// func Instruct(th *rtda.Thread, method *class.Method) (er error) {

// INVOKE_VIRTUAL 需要动态绑定的方法: 最常见的 对象的的方法的调用
type INVOKE_VIRTUAL struct {
	_16_fetch
}

func (f *INVOKE_VIRTUAL) Execute(fr *rtda.MethodFrame) {
	current := fr.Method().Class()
	cp := current.ConstantPool()
	ref := cp.GetConstant(f.Index).(*class.MethodRef)
	method := ref.ResolveMethod()

	obj := fr.OperationStack().GetRef(int32(method.ArgsCount() - 1))
	if obj == nil { // 被调用的方法对应的 `对象`
		panic("java.lang.NullPointerException")
	}

	// 校验方法
	if method.IsStatic() { // 不允许调用静态方法
		panic("java.lang.IncompatibleClassChangeError")
	}
	if method.IsSuper() { // super 关键字调用的方法
		panic(fmt.Sprintf("Error %v.%v%v 会死循环", method.Class().Name(), method.Name(), method.Descriptor()))
	}
	if method.IsProtected() {
		// TODO :: 只能被自己, 子类, 或者同一个包调用 否则: IllegalAccessError
	}
	// 查找对象对应的方法
	invoker := class.LookupMethodInClass(obj.Class(), method.Name(), method.Descriptor())
	if invoker == nil || invoker.IsAbstract() {
		panic("java.lang.AbstractMethodError")
	}

	// 创建一个新的帧, 并将参数推到新的栈中
	nf := newMethodFrame(fr, invoker)
	log.Default().Printf("Invoke VIRTUAL method %v.%v int `%v`", method.Class().Name(), method.Name(), nf.LocalVar().GetRef(0).Class().Name())
	RunThread(nf.Thread()) // to runing ......
}

// INVOKE_INTERFACE 针对接口的方法的调用, `声明的是一个接口, 被赋值为一个实现, 当调用该生命的接口的方法时`
type INVOKE_INTERFACE struct {
	_16_fetch
}

func (f *INVOKE_INTERFACE) FetchOperands(bf *tool.Buffer) {
	f._16_fetch.FetchOperands(bf)
	bf.ReadByte() // count
	bf.ReadByte() // must 0
}

func (f *INVOKE_INTERFACE) Execute(fr *rtda.MethodFrame) {
	current := fr.Method().Class()
	cp := current.ConstantPool()
	ref := cp.GetConstant(f.Index).(*class.InterfaceMethodRef)
	method := ref.ResolvedInterfaceMethod()
	// 校验方法
	if method.IsStatic() || method.IsPrivate() {
		panic("java.lang.IncompatibleClassChangeError")
	}
	obj := fr.OperationStack().GetRef(int32(method.ArgsCount() - 1))
	if obj == nil { // 被调用的方法对应的 `对象`
		panic("java.lang.NullPointerException")
	}
	if !obj.Class().IsAssignableFrom(method.Class()) { // 派生关系
		panic("java.lang.IncompatibleClassChangeError")
	}

	// 查找对象对应的方法
	invoker := class.LookupMethodInClass(obj.Class(), method.Name(), method.Descriptor())
	if invoker == nil || invoker.IsAbstract() {
		panic("java.lang.AbstractMethodError")
	}
	if method.IsStatic() || !method.IsPublic() {
		panic("java.lang.IllegalAccessError")
	}

	frame := newMethodFrame(fr, invoker)
	RunThread(frame.Thread())
}

// newMethodFrame , frame: 当前运行的方法的帧, method: 将要调用的方法, 推送参数到新的栈中
func newMethodFrame(frame *rtda.MethodFrame, method *class.Method) *rtda.MethodFrame {
	fr := rtda.NewMethodFrame(method)
	frame.Thread().PushMethodFrame(fr)
	l := method.ArgsCount()
	// slot 直接传递, 要求 long double 的数据结构是一样的
	for i := l - 1; i >= 0; i-- {
		s := frame.OperationStack().PopSlot()
		fr.LocalVar().SetSlot(uint(i), s)
	}
	return fr
}

// methodArgs 解析参数 不如直接添加一个 `fr.OperationStack().GetRef` 方法, 调用方法, 需要根据对象去查找需要执行的方法
func methodArgs(frame *rtda.MethodFrame, method *class.Method) (obj java.Slot, args []java.Slot) {
	l := method.ArgsCount()
	if method.IsStatic() {
		args = make([]java.Slot, l-1, l-1)
	} else {
		args = make([]java.Slot, l, l)
	}
	for i := range args {
		args[i] = frame.OperationStack().PopSlot()
	}
	if method.IsStatic() {
		obj = frame.OperationStack().PopSlot()
	}
	return
}
